{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hosting Capacity\n",
    "\n",
    "The term PV hosting capacity is defined as the maximum PV capacity which can be connected to a specific grid, while still complying with relevant grid codes and grid planning principles. \n",
    "\n",
    "Here we will introduce a basic algorithm to calculate PV hosting capacity with pandapower.\n",
    "\n",
    "The basic idea of calculating hosting capacity is to increase PV installation until a violation of any planning principle or constraint occurs. To analyse hosting capacity, we need three basic building blocks:\n",
    "1. Evaluating constraint violations\n",
    "2. Chosing connection points for new PV plants\n",
    "3. Defining the installed power of new PV plants "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "source": [
    "### Evaluation of constraint violations\n",
    "\n",
    "Our example function that evaluates constraint violation is defined as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T09:24:21.119957Z",
     "start_time": "2025-10-27T09:24:19.790663Z"
    }
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from numpy.random import default_rng\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "from pandapower.run import runpp\n",
    "from pandapower.create import create_sgen\n",
    "from pandapower.networks import mv_oberrhein"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T09:24:21.135973Z",
     "start_time": "2025-10-27T09:24:21.126973Z"
    }
   },
   "outputs": [],
   "source": [
    "rng = default_rng(0)\n",
    "\n",
    "def violations(net):\n",
    "    runpp(net)\n",
    "    if net.res_line.loading_percent.max() > 50:\n",
    "        return (True, \"Line \\n Overloading\")\n",
    "    elif net.res_trafo.loading_percent.max() > 50:\n",
    "        return (True, \"Transformer \\n Overloading\")\n",
    "    elif net.res_bus.vm_pu.max() > 1.04:\n",
    "        return (True, \"Voltage \\n Violation\")\n",
    "    else:\n",
    "        return (False, None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The function runs a power flow and then checks for line loading and transformer loading (both of which have to be below 50%) and for voltage rise (which has to be below 1.04 pu). The function returns a boolean flag to signal if any constraint is violated as well as a string that indicates the type of constraint violation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Chosing a connection bus\n",
    "\n",
    "If new PV plants are installed, a connection bus has to be chosen. Here, we chose one random bus of each of the buses that have a load connection:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T09:24:21.150975Z",
     "start_time": "2025-10-27T09:24:21.143976Z"
    }
   },
   "outputs": [],
   "source": [
    "def chose_bus(net):\n",
    "    return rng.choice(net.load.bus.values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Chosing a PV plant size\n",
    "\n",
    "The function that returns a plant size is given as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T09:24:21.166990Z",
     "start_time": "2025-10-27T09:24:21.157976Z"
    }
   },
   "outputs": [],
   "source": [
    "def get_plant_size_mw():\n",
    "    return rng.normal(loc=0.5, scale=0.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This function returns a random value from a normal distribution with a mean of 0.5 MW and a standard deviation of 0.05 MW. Depending on the existing information, it would also be possible to use other probability distributions, such as a Weibull distribution, or to draw values from existing plant sizes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluating Hosting Capacity\n",
    "\n",
    "We now use these building blocks to evaluate hosting capacity in a generic network. We use the MV Oberrhein network from the pandapower networks package as an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T09:24:21.182972Z",
     "start_time": "2025-10-27T09:24:21.172977Z"
    }
   },
   "outputs": [],
   "source": [
    "def load_network():\n",
    "    return mv_oberrhein(scenario=\"generation\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The hosting capacity is then evaluated like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T09:25:24.763841Z",
     "start_time": "2025-10-27T09:24:21.188978Z"
    }
   },
   "outputs": [],
   "source": [
    "iterations = 50\n",
    "results = pd.DataFrame(columns=[\"installed\", \"violation\"])\n",
    "\n",
    "for i in range(iterations):\n",
    "    net = load_network()\n",
    "    installed_mw = 0\n",
    "    while 1:\n",
    "        violated, violation_type = violations(net)\n",
    "        if violated:\n",
    "            results.loc[i] = [installed_mw, violation_type]\n",
    "            break\n",
    "        else:\n",
    "            plant_size = get_plant_size_mw()\n",
    "            create_sgen(net, chose_bus(net), p_mw=plant_size, q_mvar=0)\n",
    "            installed_mw += plant_size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This algorithm adds new PV plants until a violation of any constraint occurs. Then, it saves the installed PV capacity. This is carried out for a number of iteration (here: 50) to get a distribution of hosting capacity values depending on connection points and plant sizes.\n",
    "\n",
    "The results can be visualized using matplotlib and seaborn:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T09:25:25.093313Z",
     "start_time": "2025-10-27T09:25:24.925287Z"
    }
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "plt.rc('xtick', labelsize=18)    # fontsize of the tick labels\n",
    "plt.rc('ytick', labelsize=18)    # fontsize of the tick labels\n",
    "plt.rc('legend', fontsize=18)    # fontsize of the tick labels\n",
    "plt.rc('axes', labelsize=20)    # fontsize of the tick labels\n",
    "plt.rcParams['font.size'] = 20\n",
    "\n",
    "sns.set_style(\"whitegrid\", {'axes.grid' : False})\n",
    "fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10,5))\n",
    "ax = axes[0]\n",
    "sns.boxplot(results.installed, width=.1, ax=ax, orient=\"v\")\n",
    "ax.set_ylabel(\"Installed Capacity [MW]\")\n",
    "\n",
    "ax = axes[1]\n",
    "ax.axis(\"equal\")\n",
    "results.violation.value_counts().plot(kind=\"pie\", ax=ax,  autopct=lambda x:\"%.0f %%\"%x)\n",
    "ax.set_ylabel(\"\")\n",
    "ax.set_xlabel(\"\")\n",
    "sns.despine()\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "source": [
    "Note that this is only an example for a basic algorithm in order to demonstrate how such problems can be tackled with pandapower. Algorithms applied in real case studies might include Q-control of PV plants, transformer tap controllers, more sophisticated distribution of PV plants, probability distribution different buses, binary search for the hosting capacity evaluation etc."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
